/*
 * Copyright 2009 Lukasz Wozniak
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 *
 * http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package pl.lwozniak.blip;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import pl.lwozniak.blip.entities.Avatar;
import pl.lwozniak.blip.entities.Background;
import pl.lwozniak.blip.entities.DirectMessage;
import pl.lwozniak.blip.entities.Error;
import pl.lwozniak.blip.entities.Movie;
import pl.lwozniak.blip.entities.Notice;
import pl.lwozniak.blip.entities.Picture;
import pl.lwozniak.blip.entities.PrivateMessage;
import pl.lwozniak.blip.entities.Recording;
import pl.lwozniak.blip.entities.Shortlink;
import pl.lwozniak.blip.entities.Status;
import pl.lwozniak.blip.entities.StatusFactory;
import pl.lwozniak.blip.entities.Subscription;
import pl.lwozniak.blip.entities.User;
import pl.lwozniak.blip.message.BlipMessagePart;
import pl.lwozniak.blip.message.FileMessagePart;
import pl.lwozniak.blip.message.TextMessagePart;
import pl.lwozniak.http.AbstractHttpConnectorFactory;
import pl.lwozniak.http.Download;
import pl.lwozniak.http.HttpConnector;
import pl.lwozniak.http.HttpConnectorFactory;
import pl.lwozniak.http.HttpHeader;
import pl.lwozniak.http.HttpProxyConfig;
import pl.lwozniak.http.HttpRequestException;
import pl.lwozniak.http.HttpResult;
import pl.lwozniak.http.HttpStrings;

/**
 * Blip connector allows making requests and analyzing responses to and from
 * blip.pl
 * 
 * @author Lukasz Wozniak
 * 
 */
public class BlipConnector {
	private static final String PATH_PICTURES = "/pictures";
	private static final String PATH_BLIPOSPHERE = "/bliposphere";
	private static final String PATH_DIRECTED_MESSAGES = "/directed_messages";
	private static final String PATH_PRIVATE_MESSAGES = "/private_messages";
	private static final String PATH_UPDATES = "/updates";
	private static final String PATH_STATUSES = "/statuses";
	private static final String PATH_NOTICES = "/notices";
	private static final String PATH_TAGS = "/tags";
	private static final String PATH_SHORTLINKS = "/shortlinks";
	private static final String PATH_PROFILE = "/profile";
	private static final String PATH_DASHBOARD = "/dashboard";
	private static final String PATH_USERS = "/users";
	private static final String PATH_AVATAR = "/avatar";
	private static final String PATH_BACKGROUND = "/background";
	private static final String PATH_SUBSCRIPTIONS = "/subscriptions";
	private static final int METHOD_GET = 1;
	private static final int METHOD_POST = 2;
	private static final int METHOD_PUT = 3;
	private static final int METHOD_DELETE = 4;

	private static final String BLIP_URL = "http://api.blip.pl";
	private String username;
	private String authentication;
	private HttpConnector connector;
	private final String baseUrl;

	private static final String LIBRARY_VERSION = "yajba 0.2.5";
	/**
	 * Maximum number of returned records.<br/>
	 * API limit enforced by Server.
	 */
	public static final int MAX_LIMIT = 50;
	/**
	 * Flag marking subscription channel WWW
	 */
	public static final int SUBS_CHANNEL_WWW = 1;
	/**
	 * Flag marking subscription channel Instant Messenger
	 */
	public static final int SUBS_CHANNEL_IM = 2;
	/**
	 * Flag marking subscription direction to user
	 */
	public static final int SUBS_DIRECTION_TO = 1;
	/**
	 * Flag marking subscription direction from user
	 */
	public static final int SUBS_DIRECTION_FROM = 2;

	public static final int INCLUDE_USER = 1;
	public static final int INCLUDE_RECIPENT = 2;
	public static final int INCLUDE_PICTURES = 4;
	public static final int INCLUDE_USER_AVATAR = 8;
	public static final int INCLUDE_RECIPENT_AVATAR = 16;

	private final HttpHeader[] DEFAULT_HEADERS;

	private HttpHeader[] authHeaders;

	private HttpHeader[] prepareDefaultHeaders(String client) {
		String userAgent = LIBRARY_VERSION;
		if (client != null) {
			userAgent = userAgent + "(" + client + ")";
		}
		return new HttpHeader[] {
				new HttpHeader("User-Agent", userAgent),
				new HttpHeader("X-Blip-API", "0.02"),
				new HttpHeader("Accept-Charset", HttpConnector.DEFAULT_ENCODING),
				new HttpHeader("Accept", "application/json") };
	}

	/**
	 * Constructor for unauthenticated access to blip.<br/>
	 * API methods may throw {@link BlipUnauthorizedException} when
	 * authorization is necessary.
	 * 
	 * @param proxy
	 *            proxy configuration accepts null if no configuration needed
	 * @param customUrl
	 *            custom api URL
	 * @param client
	 *            name of client using this api (will be appended to User-Agent
	 *            header)
	 * @throws BlipConnectorException
	 */
	public BlipConnector(HttpProxyConfig proxy, String customUrl, String client)
			throws BlipConnectorException {
		super();
		DEFAULT_HEADERS = prepareDefaultHeaders(client);
		HttpConnectorFactory f = AbstractHttpConnectorFactory.newInstance();
		f.setHttpProxyConfig(proxy);
		connector = f.getConnector();
		connector.setDefaultHeaders(DEFAULT_HEADERS);
		baseUrl = (customUrl == null ? BLIP_URL : customUrl);

	}

	/**
	 * Constructor for unauthenticated access to blip.<br/>
	 * API methods may throw {@link BlipUnauthorizedException} when
	 * authorization is necessary. No client information, just library version
	 * in User-Agent header.
	 * 
	 * @param proxy
	 *            proxy configuration accepts null if no configuration needed
	 * @param customUrl
	 *            custom api URL
	 * @throws BlipConnectorException
	 */
	public BlipConnector(HttpProxyConfig proxy, String customUrl)
			throws BlipConnectorException {
		this(proxy, customUrl, null);
	}

	/**
	 * Full configuration constructor.<br/>
	 * For authenticated access.
	 * 
	 * @param username
	 *            blip account login
	 * @param password
	 *            blip account password
	 * @param proxy
	 *            proxy configuration accepts null if no configuration needed
	 * @param customUrl
	 *            custom api URL
	 * @param client
	 *            name of client using this api (will be appended to User-Agent
	 *            header)
	 * @throws BlipConnectorException
	 */
	public BlipConnector(String username, String password,
			HttpProxyConfig proxy, String customUrl, String client)
			throws BlipConnectorException {
		this(proxy, customUrl, client);
		this.username = username;
		String auth = username + ":" + password;
		try {
			auth = new String(Encoder.encode(auth.getBytes("UTF-8")), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			// Java w/o UTF-8?
		}
		this.authentication = auth;
		authHeaders = new HttpHeader[] { new HttpHeader("Authorization",
				"Basic " + authentication) };
	}

	/**
	 * Full configuration constructor.<br/>
	 * For authenticated access. No client information, just library version in
	 * User-Agent header.
	 * 
	 * @param username
	 *            blip account login
	 * @param password
	 *            blip account password
	 * @param proxy
	 *            proxy configuration accepts null if no configuration needed
	 * @param customUrl
	 *            custom api URL
	 * @throws BlipConnectorException
	 */
	public BlipConnector(String username, String password,
			HttpProxyConfig proxy, String customUrl)
			throws BlipConnectorException {
		this(username, password, proxy, customUrl, null);
	}

	/**
	 * Executes HTTP requests.
	 * 
	 * @param method
	 * @param path
	 * @param body
	 * @param auth
	 * @return result holder
	 * @throws BlipConnectorException
	 */
	private HttpResult execute(int method, URI path, BlipMessagePart body,
			Boolean auth) throws BlipConnectorException {
		// if execution requires authorization and API is used anonymously be
		// nasty...
		if (auth != null && auth.booleanValue() && authHeaders == null) {
			throw new BlipUnauthorizedException(401, "Unauthorized");
		}
		// if auth is unset set true, headers will be sent if present
		if (auth == null) {
			auth = Boolean.TRUE;
		}

		try {
			HttpResult res = null;
			switch (method) {
			case METHOD_GET:

				res = connector.doGet(auth.booleanValue() ? authHeaders : null,
						path, body);
				break;
			case METHOD_POST:

				res = connector.doPost(
						auth.booleanValue() ? authHeaders : null, path, body);
				break;
			case METHOD_PUT:

				res = connector.doPut(auth.booleanValue() ? authHeaders : null,
						path, body);
				break;
			case METHOD_DELETE:

				res = connector.doDelete(auth.booleanValue() ? authHeaders
						: null, path, body);
				break;

			default:
				throw new IllegalArgumentException("Unknown HTTP method "
						+ method);
			}
			return res;
		} catch (HttpRequestException e) {
			throw new BlipConnectorException(e);
		}

	}

	/**
	 * Handles result, throws exceptions according to HTTP code.
	 * 
	 * @param result
	 *            request result
	 * @return body of request, may be null.
	 * @throws BlipUnauthorizedException
	 * @throws BlipNotFoundException
	 * @throws BlipConnectorException
	 */
	private String handleResult(HttpResult result)
			throws BlipUnauthorizedException, BlipNotFoundException,
			BlipConnectorException {
		switch (result.getCode()) {
		// good codes
		case 200:
			return result.getBody();
		case 201:
			return result.getBody();
			// bad but expected codes
		case 401:
			throw new BlipUnauthorizedException(result.getCode(), result
					.getMessage());
		case 404:
			throw new BlipNotFoundException(result.getCode(), result
					.getMessage());
			// bad request, try to handle
		case 400:
			throw new BlipBadRequestException(result.getCode(), result
					.getMessage(), extractError(result.getBody()));
			// bad! errors

		case 422:
		case 503:
		case 500:
		default:
			throw new BlipConnectorException(result.getCode(), result
					.getMessage());
		}
	}

	private Error extractError(String body) {
		if (body != null && body.length() > 0 && body.charAt(0) == '{') {
			try {
				return new Error(new JSONObject(body));
			} catch (JSONException e) {
				// if it's not blip error JSON object then ignore
			}
		}
		return null;
	}

	/**
	 * Appends to built url parameters limiting returned results.
	 * 
	 * @param sb
	 *            built url
	 * @param limit
	 *            of records to return, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last record returned, 0 means no id
	 */
	private void appendLimitingUrlParts(StringBuilder sb, int limit,
			int offset, long since, int include) {
		if (since > 0) {
			sb.append("/").append("/since").append("/").append(since);
		}
		byte pos = 0;
		if (limit > 0) {
			appendUrlParam(sb, "limit", Integer.toString(limit), pos++);
		}
		if (offset > 0) {
			appendUrlParam(sb, "offset", Integer.toString(offset), pos++);
		}
		if (include > 0) {
			appendUrlParam(sb, "include", buildIncludes(include), pos++);
		}
	}

	/**
	 * Builds includes parameter string.
	 * 
	 * @param include
	 *            flag value
	 * 
	 * @return include parameter value
	 */
	private String buildIncludes(int include) {
		StringBuilder sb = new StringBuilder();
		if ((include & INCLUDE_PICTURES) == INCLUDE_PICTURES) {
			if (sb.length() > 0) {
				sb.append(',');
			}
			sb.append("pictures");
		}
		if ((include & INCLUDE_USER) == INCLUDE_USER) {
			if (sb.length() > 0) {
				sb.append(',');
			}
			sb.append("user");
			if ((include & INCLUDE_USER_AVATAR) == INCLUDE_USER_AVATAR) {
				sb.append(",user[avatar]");
			}
		}
		if ((include & INCLUDE_RECIPENT) == INCLUDE_RECIPENT) {
			if (sb.length() > 0) {
				sb.append(',');
			}
			sb.append("recipent");
			if ((include & INCLUDE_RECIPENT_AVATAR) == INCLUDE_RECIPENT_AVATAR) {
				sb.append(",recipent[avatar]");
			}
		}
		return sb.toString();
	}

	/**
	 * Appends to built url parameters limiting returned results for queries for
	 * unfiltered resources.
	 * 
	 * @param sb
	 *            built url
	 * @param limit
	 *            of records to return if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last record returned, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 */
	private void appendLimitingUrlPartsForAll(StringBuilder sb, int limit,
			int offset, long since, int include) {
		if (since > 0) {
			sb.append("/").append(since).append("/all_since");
		} else {
			sb.append("/all");
		}
		byte pos = 0;
		if (limit > 0) {
			appendUrlParam(sb, "limit", Integer.toString(limit), pos++);
		}
		if (offset > 0) {
			appendUrlParam(sb, "offset", Integer.toString(offset), pos++);
		}
		if (include > 0) {
			appendUrlParam(sb, "include", buildIncludes(include), pos++);
		}
	}

	private void appendUrlParam(StringBuilder sb, String name, String value,
			byte pos) {
		sb.append(pos > 0 ? '&' : '?');
		sb.append(name);
		sb.append(HttpStrings.EQUALS);
		sb.append(value);
	}

	public Download getResource(URI target) throws BlipConnectorException {
		try {
			return connector.doDownload(DEFAULT_HEADERS, target);
		} catch (HttpRequestException e) {
			throw new BlipConnectorException(e);
		}
	}

	/**
	 * Returns updates from all users.
	 * 
	 * @param limit
	 *            how many updates, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last update, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getAllUpdates(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES);
			appendLimitingUrlPartsForAll(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getUpdates(uri);
	}

	/**
	 * Returns current users updates.
	 * 
	 * @param limit
	 *            how many updates, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last update, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getUpdates(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getUpdates(uri);
	}

	/**
	 * Returns updates for specified user.
	 * 
	 * @param limit
	 *            how many updates, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last update, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @param user
	 *            username whose updates we want to get.
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getUserUpdates(int limit, int offset, long since,
			int include, String user) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(user).append(PATH_UPDATES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getUpdates(uri);
	}

	/**
	 * Returns updates for uri.
	 * 
	 * @param uri
	 *            uri of resource to call
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	private List<Status> getUpdates(URI uri) throws BlipConnectorException {
		HttpResult out = execute(METHOD_GET, uri, null, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Status> ret = new ArrayList<Status>(array.length());
				for (int i = 0; i < array.length(); i++) {
					// we can have here normal statuses, private and directed
					// messages so use factory:
					ret.add(StatusFactory.createStatus(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns update for specified id.
	 * 
	 * @param id
	 *            update identifier
	 * @return {@link Status}
	 * @throws BlipNotFoundException
	 *             if no such update
	 * @throws BlipConnectorException
	 *             for general error
	 */
	public Status getUpdate(long id) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(id).toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Deletes update with given id.
	 * 
	 * @param id
	 *            update identifier
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public void deleteUpdate(long id) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(id);
			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult res = execute(METHOD_DELETE, uri, null, true);
		handleResult(res);
	}

	/**
	 * Creates new status with given body.
	 * 
	 * @param content
	 *            status content to post
	 * @return created status
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public Status createUpdate(String content) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_UPDATES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		TextMessagePart part = new TextMessagePart("update[body]", content);
		HttpResult out = execute(METHOD_POST, uri, part, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}

	}

	/**
	 * Creates new status with given body. Attachment is sent as
	 * <code>application/octet-stream</code> no mime magic.
	 * 
	 * @param content
	 *            status content to post
	 * @param attach
	 *            stream to attachment
	 * @param filename
	 *            of attachment
	 * @param contentType
	 *            MIME content type of attachment may be null, then default
	 *            application/octet-stream is used
	 * @param size
	 *            file size
	 * @return created {@link Status}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public Status createUpdate(String content, InputStream attach,
			String filename, String contentType, long size)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_UPDATES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new FileMessagePart(contentType,
				"update[picture]", filename, attach, size);
		part.addNext(new TextMessagePart("update[body]", content));
		HttpResult out = execute(METHOD_POST, uri, part, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}

	}

	/**
	 * Creates update directed to user by prepending >user: to message. It's
	 * generally better to use createDirectedMessage().
	 * 
	 * @param content
	 *            message content
	 * @param target
	 *            target user
	 * @return created {@link Status}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipBadRequestException
	 *             when target user does not exist
	 * @throws BlipConnectorException
	 *             for general errors
	 */
	public Status createUpdate(String content, String target)
			throws BlipConnectorException {
		return createUpdate(">" + target + ":" + content);
	}

	/**
	 * Creates directed message.
	 * 
	 * @param content
	 *            message content
	 * @param target
	 *            target user
	 * @return created {@link DirectMessage}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipBadRequestException
	 *             when target user does not exist
	 * @throws BlipConnectorException
	 *             for general errors
	 */
	public DirectMessage createDirectedMessage(String content, String target)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_DIRECTED_MESSAGES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		TextMessagePart part = new TextMessagePart("directed_message[body]",
				content);
		part
				.addNext(new TextMessagePart("directed_message[recipient]",
						target));
		HttpResult out = execute(METHOD_POST, uri, part, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new DirectMessage(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}
	}

	/**
	 * Creates directed message.
	 * 
	 * @param content
	 *            message content
	 * @param target
	 *            target user
	 * @param attach
	 *            stream to attachment
	 * @param filename
	 *            of attachment
	 * @param contentType
	 *            MIME content type of attachment may be null, then default
	 *            application/octet-stream is used
	 * @param size
	 *            file size
	 * @return created {@link DirectMessage}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipBadRequestException
	 *             when target user does not exist
	 * @throws BlipConnectorException
	 *             for general errors
	 */
	public DirectMessage createDirectedMessage(String content, String target,
			InputStream attach, String filename, String contentType, long size)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_DIRECTED_MESSAGES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new FileMessagePart(contentType,
				"directed_message[picture]", filename, attach, size);

		part.addNext(new TextMessagePart("directed_message[body]", content));
		part
				.addNext(new TextMessagePart("directed_message[recipient]",
						target));
		HttpResult out = execute(METHOD_POST, uri, part, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new DirectMessage(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}
	}

	/**
	 * Returns directed message for specified id.
	 * 
	 * @param id
	 *            message identifier
	 * @return {@link DirectMessage}
	 * @throws BlipNotFoundException
	 *             if no such message
	 * @throws BlipConnectorException
	 *             for general error
	 */
	public DirectMessage getDirectedMessage(long id)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(new StringBuilder().append(baseUrl).append(
					PATH_DIRECTED_MESSAGES).append("/").append(id).toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new DirectMessage(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Deletes directed message with given id.
	 * 
	 * @param id
	 *            message identifier
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public void deleteDirectedMessage(long id) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_DIRECTED_MESSAGES).append("/").append(id);
			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult res = execute(METHOD_DELETE, uri, null, true);
		handleResult(res);
	}

	/**
	 * Returns directed messages for uri.
	 * 
	 * @param uri
	 *            resource uri to call
	 * @return {@link List} of {@link DirectMessage}
	 * @throws BlipConnectorException
	 */
	private List<DirectMessage> getDirectedMessages(URI uri)
			throws BlipConnectorException {
		HttpResult out = execute(METHOD_GET, uri, null, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<DirectMessage> ret = new ArrayList<DirectMessage>(
						array.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new DirectMessage(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns directed messages from all users.
	 * 
	 * @param limit
	 *            how many updates, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last update, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of {@link DirectMessage}
	 * @throws BlipConnectorException
	 */
	public List<DirectMessage> getAllDirectedMessages(int limit, int offset,
			long since, int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_DIRECTED_MESSAGES);
			appendLimitingUrlPartsForAll(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getDirectedMessages(uri);
	}

	/**
	 * Returns current users directed messages.
	 * 
	 * @param limit
	 *            how many updates, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last update, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of {@link DirectMessage}
	 * @throws BlipConnectorException
	 */
	public List<DirectMessage> getDirectedMessages(int limit, int offset,
			long since, int include) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_DIRECTED_MESSAGES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getDirectedMessages(uri);
	}

	/**
	 * Returns directed messages for specified user.
	 * 
	 * @param limit
	 *            how many updates, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last update, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @param user
	 *            username whose updates we want to get.
	 * @return {@link List} of {@link DirectMessage}
	 * @throws BlipConnectorException
	 */
	public List<DirectMessage> getUserDirectedMessages(int limit, int offset,
			long since, int include, String user) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(user).append(
					PATH_DIRECTED_MESSAGES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getDirectedMessages(uri);
	}

	// ******************************* Statuses
	/**
	 * Returns statuses from all users.
	 * 
	 * @param limit
	 *            how many statuses, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last status, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getAllStatuses(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_STATUSES);
			appendLimitingUrlPartsForAll(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getStatuses(uri);
	}

	/**
	 * Returns current users statuses.
	 * 
	 * @param limit
	 *            how many statuses, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last status, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getStatuses(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_STATUSES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getStatuses(uri);
	}

	/**
	 * Returns statuses for specified user.
	 * 
	 * @param limit
	 *            how many statuses, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last status, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @param user
	 *            username whose statuses we want to get.
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getUserStatuses(int limit, int offset, long since,
			int include, String user) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(user).append(PATH_STATUSES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getStatuses(uri);
	}

	/**
	 * Returns statuses for uri.
	 * 
	 * @param uri
	 *            uri of resource to call
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	private List<Status> getStatuses(URI uri) throws BlipConnectorException {
		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Status> ret = new ArrayList<Status>(array.length());
				for (int i = 0; i < array.length(); i++) {
					// we can have here normal statuses, private and directed
					// messages so use factory:
					ret.add(StatusFactory.createStatus(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns update for specified id.
	 * 
	 * @param id
	 *            update identifier
	 * @return {@link Status}
	 * @throws BlipNotFoundException
	 *             if no such update
	 * @throws BlipConnectorException
	 *             for general error
	 */
	public Status getStatus(long id) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(new StringBuilder().append(baseUrl).append(
					PATH_STATUSES).append("/").append(id).toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Deletes status with given id.
	 * 
	 * @param id
	 *            update identifier
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public void deleteStatus(long id) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_STATUSES).append("/").append(id);
			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult res = execute(METHOD_DELETE, uri, null, true);
		handleResult(res);
	}

	/**
	 * Creates new status with given body.
	 * 
	 * @param content
	 *            status content to post
	 * @return created status
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public Status createStatus(String content) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_STATUSES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		TextMessagePart part = new TextMessagePart("status[body]", content);
		HttpResult out = execute(METHOD_POST, uri, part, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}

	}

	/**
	 * Creates new status with given body. Attachment is sent as
	 * <code>application/octet-stream</code> no mime magic.
	 * 
	 * @param content
	 *            status content to post
	 * @param attach
	 *            stream to attachment
	 * @param filename
	 *            of attachment
	 * @param contentType
	 *            MIME content type of attachment may be null, then default
	 *            application/octet-stream is used
	 * @param size
	 *            file size
	 * @return created {@link Status}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public Status createStatus(String content, InputStream attach,
			String filename, String contentType, long size)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_STATUSES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new FileMessagePart(contentType,
				"status[picture]", filename, attach, size);
		part.addNext(new TextMessagePart("status[body]", content));
		HttpResult out = execute(METHOD_POST, uri, part, true);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Status(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}

	}

	// ******************************** PrivateMessage
	/**
	 * Creates private message.
	 * 
	 * @param content
	 *            message content
	 * @param target
	 *            target user
	 * @return created {@link PrivateMessage}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipBadRequestException
	 *             when target user does not exist
	 * @throws BlipConnectorException
	 *             for general errors
	 */
	public PrivateMessage createPrivateMessage(String content, String target)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_PRIVATE_MESSAGES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		TextMessagePart part = new TextMessagePart("private_message[body]",
				content);
		part.addNext(new TextMessagePart("private_message[recipient]", target));
		HttpResult out = execute(METHOD_POST, uri, part, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new PrivateMessage(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}
	}

	/**
	 * Creates private message.
	 * 
	 * @param content
	 *            message content
	 * @param target
	 *            target user
	 * @param attach
	 *            stream to attachment
	 * @param filename
	 *            of attachment
	 * @param contentType
	 *            MIME content type of attachment may be null, then default
	 *            application/octet-stream is used
	 * @param size
	 *            file size
	 * @return created {@link PrivateMessage}
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipBadRequestException
	 *             when target user does not exist
	 * @throws BlipConnectorException
	 *             for general errors
	 */
	public PrivateMessage createPrivateMessage(String content, String target,
			InputStream attach, String filename, String contentType, long size)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_PRIVATE_MESSAGES);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		TextMessagePart part = new TextMessagePart("private_message[body]",
				content);
		part.addNext(new TextMessagePart("private_message[recipient]", target));
		part.addNext(new FileMessagePart(contentType,
				"private_message[picture]", filename, attach, size));

		HttpResult out = execute(METHOD_POST, uri, part, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new PrivateMessage(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			throw new BlipConnectorException("Unexpected error");
		}
	}

	/**
	 * Deletes private message with given id.
	 * 
	 * @param id
	 *            private message identifier
	 * @throws BlipUnauthorizedException
	 *             when not authorized to do so
	 * @throws BlipNotFoundException
	 *             when no such update
	 * @throws BlipConnectorException
	 */
	public void deletePrivateMessage(long id) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_PRIVATE_MESSAGES).append("/").append(id);
			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult res = execute(METHOD_DELETE, uri, null, Boolean.TRUE);
		handleResult(res);
	}

	/**
	 * Returns private message for specified id.
	 * 
	 * @param id
	 *            message identifier
	 * @return {@link DirectMessage}
	 * @throws BlipNotFoundException
	 *             if no such message
	 * @throws BlipConnectorException
	 *             for general error
	 */
	public PrivateMessage getPrivateMessage(long id)
			throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(new StringBuilder().append(baseUrl).append(
					PATH_PRIVATE_MESSAGES).append("/").append(id).toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new PrivateMessage(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns current users private messages.
	 * 
	 * @param limit
	 *            how many messages, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last messages, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of {@link PrivateMessage}
	 * @throws BlipConnectorException
	 */
	public List<PrivateMessage> getPrivateMessages(int limit, int offset,
			long since, int include) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_PRIVATE_MESSAGES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<PrivateMessage> ret = new ArrayList<PrivateMessage>(
						array.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new PrivateMessage(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	// ********************************* Bliposphere

	/**
	 * Returns current Bliposphere content. Authorized or Unauhorized.
	 * 
	 * @return {@link List} of type {@link Status}
	 * @throws BlipConnectorException
	 */
	public List<Status> getBliposphere() throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(baseUrl + PATH_BLIPOSPHERE);
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getStatuses(uri);
	}

	// *************************** Notices
	/**
	 * Returns notices from all users.
	 * 
	 * @param limit
	 *            how many notices, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last notice, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Notice} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Notice> getAllNotices(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_NOTICES);
			appendLimitingUrlPartsForAll(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getNotices(uri);
	}

	/**
	 * Returns current users notices.
	 * 
	 * @param limit
	 *            how many notices, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last notice, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Notice} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Notice> getNotices(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_NOTICES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getNotices(uri);
	}

	/**
	 * Returns notices for specified user.
	 * 
	 * @param limit
	 *            how many notices, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last notice, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @param user
	 *            username whose statuses we want to get.
	 * @return {@link List} of type {@link Notice} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Notice> getUserNotices(int limit, int offset, long since,
			int include, String user) throws BlipConnectorException {
		if (limit > MAX_LIMIT) {
			limit = MAX_LIMIT;
		}

		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(user).append(PATH_NOTICES);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getNotices(uri);
	}

	/**
	 * Returns notices for uri.
	 * 
	 * @param uri
	 *            uri of resource to call
	 * @return {@link List} of type {@link Notice} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	private List<Notice> getNotices(URI uri) throws BlipConnectorException {
		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Notice> ret = new ArrayList<Notice>(array.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new Notice(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns notice for specified id.
	 * 
	 * @param id
	 *            notice identifier
	 * @return {@link Notice}
	 * @throws BlipNotFoundException
	 *             if no such update
	 * @throws BlipConnectorException
	 *             for general error
	 */
	public Notice getNotice(long id) throws BlipConnectorException {
		URI uri;
		try {
			uri = new URI(new StringBuilder().append(baseUrl).append(
					PATH_NOTICES).append("/").append(id).toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Notice(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	// ****************************** Tags
	/**
	 * Returns tagged statuses.
	 * 
	 * @param limit
	 *            how many statuses, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last status with this tag, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Status} or <code>null</code> if none
	 *         available
	 * @throws BlipConnectorException
	 */
	public List<Status> getTagStatuses(int limit, int offset, long since,
			int include, String tag) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_TAGS).append("/").append(tag);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		return getStatuses(uri);
	}

	// ********************** Multimedia

	/**
	 * Returns movie associated with status.
	 * 
	 * @param statusId
	 *            id of status with movie
	 * @return {@link Movie}
	 * @throws BlipNotFoundException
	 *             if no such status or no movie attached to it
	 * @throws BlipConnectorException
	 */
	public Movie getMovie(long statusId) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(statusId).append("/")
					.append("/movie");

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Movie(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns recording associated with status.
	 * 
	 * @param statusId
	 *            id of status with recording
	 * @return {@link Recording}
	 * @throws BlipNotFoundException
	 *             if no such status or no recording attached to it
	 * @throws BlipConnectorException
	 */
	public Recording getRecording(long statusId) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(statusId).append("/")
					.append("/recording");

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Recording(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns pictures associated with status.
	 * 
	 * @param statusId
	 *            id of status with pictures
	 * @return {@link List} of type {@link Picture}
	 * @throws BlipNotFoundException
	 *             if no such status or no pictures attached to it
	 * @throws BlipConnectorException
	 */
	public List<Picture> getPictures(long statusId)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_UPDATES).append("/").append(statusId).append("/")
					.append(PATH_PICTURES);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				// did we gat array or single object
				ArrayList<Picture> ret = null;
				if (res.charAt(0) == '[') {
					JSONArray array = new JSONArray(res);
					ret = new ArrayList<Picture>(array.length());
					for (int i = 0; i < array.length(); i++) {
						ret.add(new Picture(array.getJSONObject(i)));
					}
				} else {
					ret = new ArrayList<Picture>(1);
					ret.add(new Picture(new JSONObject(res)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns pictures from all users.
	 * 
	 * @param limit
	 *            how many pictures, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last picture, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Picture}
	 * @throws BlipNotFoundException
	 *             if no such picture
	 * @throws BlipConnectorException
	 */
	public List<Picture> getAllPictures(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_PICTURES);
			appendLimitingUrlPartsForAll(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				// did we gat array or single object
				ArrayList<Picture> ret = null;
				if (res.charAt(0) == '[') {
					JSONArray array = new JSONArray(res);
					ret = new ArrayList<Picture>(array.length());
					for (int i = 0; i < array.length(); i++) {
						ret.add(new Picture(array.getJSONObject(i)));
					}
				} else {
					ret = new ArrayList<Picture>(1);
					ret.add(new Picture(new JSONObject(res)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	// ******************************** Shortlinks

	/**
	 * Creates new shortlink from URL.
	 * 
	 * @param url
	 *            to shorten
	 * @return {@link Shortlink}
	 * @throws BlipConnectorException
	 */
	public Shortlink createShortlink(String url) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_SHORTLINKS);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new TextMessagePart("shortlink[original_link]",
				url);
		HttpResult out = execute(METHOD_POST, uri, part, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Shortlink(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns list of shortlinks created by all users
	 * 
	 * @param limit
	 *            how many shortlinks, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last shortlink, 0 means no id
	 * @return {@link List} of type {@link Shortlink}
	 * @throws BlipConnectorException
	 */
	public List<Shortlink> getAllShortlinks(int limit, int offset, long since)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_SHORTLINKS);
			appendLimitingUrlPartsForAll(sb, limit, offset, since, 0);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Shortlink> ret = new ArrayList<Shortlink>(array
						.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new Shortlink(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	// ***************************** Dashboard

	/**
	 * Returns current dashboard state of user.
	 * 
	 * @param limit
	 *            how many entries, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last entry, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @return {@link List} of type {@link Status}
	 * @throws BlipConnectorException
	 */
	public List<Status> getDashboard(int limit, int offset, long since,
			int include) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_DASHBOARD);
			appendLimitingUrlParts(sb, limit, offset, since, include);
			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getStatuses(uri);
	}

	/**
	 * Returns dashboard state of user.
	 * 
	 * @param limit
	 *            how many entries, if 0 server decides how many to return
	 * @param offset
	 *            offset of returned records, 0 means newest
	 * @param since
	 *            id of last entry, 0 means no id
	 * @param include
	 *            optional inclusion flags for example
	 *            {@link BlipConnector#INCLUDE_USER} , 0 means no inclusions
	 * @param username
	 *            whose dashboard is requested
	 * @return {@link List} of type {@link Status}
	 * @throws BlipConnectorException
	 */
	public List<Status> getUserDashboard(int limit, int offset, long since,
			int include, String username) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(username).append(
					PATH_DASHBOARD);
			appendLimitingUrlParts(sb, limit, offset, since, include);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		return getStatuses(uri);
	}

	// ***************************** Users
	/**
	 * Returns current user profile.
	 * 
	 * @return {@link User}
	 * @throws BlipUnauthorizedException
	 *             if profile for unauthenticated user is requested
	 * @throws BlipConnectorException
	 */
	public User getProfile() throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_PROFILE);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new User(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns user profile.
	 * 
	 * @param username
	 *            login of user whose details are to be returned
	 * @return {@link User}
	 * @throws BlipNotFoundException
	 *             if no such user
	 * @throws BlipConnectorException
	 */
	public User getUser(String username) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(username);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new User(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	// ********************************* Avatars

	/**
	 * Returns users avatar information.
	 * 
	 * @param username
	 *            of user whose avatar is requested
	 * @return {@link Avatar}
	 * @throws BlipConnectorException
	 */
	public Avatar getUserAvatar(String username) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(username)
					.append(PATH_AVATAR);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Avatar(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns avatar of current user.
	 * 
	 * @return {@link Avatar}
	 * @throws BlipUnauthorizedException
	 *             if user is not logged in
	 * @throws BlipConnectorException
	 */
	public Avatar getAvatar() throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_AVATAR);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Avatar(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Deletes current users avatar.
	 * 
	 * @throws BlipUnauthorizedException
	 *             if user is not logged in
	 * @throws BlipConnectorException
	 */
	public void deleteAvatar() throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_AVATAR);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		execute(METHOD_DELETE, uri, null, Boolean.TRUE);

	}

	/**
	 * Creates or replaces current users avatar.
	 * 
	 * @param stream
	 *            binary stream supplying avatar data
	 * @param fileName
	 *            name of file containing avatar, optional
	 * @param contentType
	 *            MIME content type of avatar, if null application/octet-stream
	 *            is used
	 * @param fileSize
	 *            number of bytes in file
	 * @return {@link Avatar} actually returns always null as API sends empty
	 *         JSON as response
	 * @throws BlipUnauthorizedException
	 *             if user is not logged in
	 * @throws BlipConnectorException
	 */
	public Avatar createAvatar(InputStream stream, String fileName,
			String contentType, long fileSize) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_AVATAR);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new FileMessagePart(contentType, "avatar[file]",
				fileName, stream, fileSize);

		// HttpResult out =
		execute(METHOD_POST, uri, part, Boolean.TRUE);
		// String res = handleResult(out);
		// if (res != null) {
		// try {
		// return new Avatar(new JSONObject(res));
		// } catch (JSONException e) {
		// throw new BlipConnectorException(e);
		// }
		// } else {
		// return null;
		// }

		// API returns empty JSON object so we return null it may change in the
		// future
		return null;
	}

	// ********************************* Backgrounds

	/**
	 * Returns users background.
	 * 
	 * @param username
	 *            of user whose background is requested
	 * @return {@link Background}
	 * @throws BlipConnectorException
	 */
	public Background getUserBackground(String username)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(username).append(
					PATH_BACKGROUND);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Background(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns current users background.
	 * 
	 * @return {@link Background}
	 * @throws BlipUnauthorizedException
	 *             if user is not logged in
	 * @throws BlipConnectorException
	 */
	public Background getBackground() throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_BACKGROUND);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				return new Background(new JSONObject(res));
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Deletes current users background.
	 * 
	 * @throws BlipUnauthorizedException
	 *             if user is not logged in
	 * @throws BlipConnectorException
	 */
	public void deleteBackground() throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_BACKGROUND);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		execute(METHOD_DELETE, uri, null, Boolean.TRUE);

	}

	/**
	 * Creates or replaces current users background.
	 * 
	 * @param stream
	 *            binary stream supplying background data
	 * @param fileName
	 *            name of file containing background, optional
	 * @param contentType
	 *            MIME content type of background, if null
	 *            application/octet-stream is used
	 * @param fileSize
	 *            number of bytes in file
	 * @return {@link Background} actually returns always null as API sends
	 *         empty JSON as response
	 * @throws BlipUnauthorizedException
	 *             if user is not logged in
	 * @throws BlipConnectorException
	 */
	public Background createBackground(InputStream stream, String fileName,
			String contentType, long fileSize) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_BACKGROUND);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new FileMessagePart(contentType,
				"background[file]", fileName, stream, fileSize);

		// HttpResult out =
		execute(METHOD_POST, uri, part, Boolean.TRUE);
		// String res = handleResult(out);
		// if (res != null) {
		// try {
		// return new Avatar(new JSONObject(res));
		// } catch (JSONException e) {
		// throw new BlipConnectorException(e);
		// }
		// } else {
		// return null;
		// }

		// API returns empty JSON object so we return null it may change in the
		// future
		return null;
	}

	// ********************************* Subscriptions

	/**
	 * Creates subscription from current user to another user.
	 * 
	 * @param user
	 *            who is to be subscribed
	 * @param chanOptions
	 *            flags indicating transports of subscription
	 *            {@link BlipConnector#SUBS_CHANNEL_IM} and
	 *            {@link BlipConnector#SUBS_CHANNEL_IM}. 0 means both
	 * 
	 * @throws BlipUnauthorizedException
	 *             when user is not logged in
	 * @throws BlipConnectorException
	 */
	public void setSubscription(String user, int chanOptions)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_SUBSCRIPTIONS).append("/").append(user);
			;

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}

		BlipMessagePart part = new TextMessagePart("id", user);
		if (((chanOptions & SUBS_CHANNEL_WWW) == SUBS_CHANNEL_WWW)
				|| chanOptions == 0) {
			part.addNext(new TextMessagePart("subscription[www]", "1"));
		} else {
			part.addNext(new TextMessagePart("subscription[www]", "0"));
		}
		if (((chanOptions & SUBS_CHANNEL_IM) == SUBS_CHANNEL_IM)
				|| chanOptions == 0) {
			part.addNext(new TextMessagePart("subscription[im]", "1"));
		} else {
			part.addNext(new TextMessagePart("subscription[im]", "0"));
		}

		execute(METHOD_PUT, uri, part, Boolean.TRUE);

	}

	/**
	 * Deletes current users subscription of another user.
	 * 
	 * @param user
	 *            username to whom subscription is to be deleted
	 * @throws BlipUnauthorizedException
	 *             when user is not logged in
	 * @throws BlipConnectorException
	 */
	public void deleteSubscription(String user) throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_SUBSCRIPTIONS).append("/").append(user);

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		execute(METHOD_DELETE, uri, null, Boolean.TRUE);
	}

	/**
	 * Return current user subscriptions
	 * 
	 * @param dirOptions
	 *            subscription direction flags
	 *            {@link BlipConnector#SUBS_DIRECTION_FROM} and
	 *            {@link BlipConnector#SUBS_DIRECTION_TO}. 0 also means both.
	 * @return {@link List} of type {@link Subscription} or null
	 * @throws BlipUnauthorizedException
	 *             when user is not logged in
	 * @throws BlipConnectorException
	 */
	public List<Subscription> getSubscriptions(int dirOptions)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_SUBSCRIPTIONS);
			if (dirOptions != 0
					&& dirOptions != (SUBS_DIRECTION_FROM | SUBS_DIRECTION_TO)) {
				if ((dirOptions & SUBS_DIRECTION_FROM) == SUBS_DIRECTION_FROM) {
					sb.append("/from");
				} else {
					sb.append("/to");
				}
			}// else no url mod, get ALL

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult out = execute(METHOD_GET, uri, null, Boolean.TRUE);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Subscription> ret = new ArrayList<Subscription>(array
						.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new Subscription(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

	/**
	 * Returns list of subscriptions of user.
	 * 
	 * @param user
	 *            username of user whose subscriptions are requested
	 * @param dirOptions
	 *            subscription direction flags
	 *            {@link BlipConnector#SUBS_DIRECTION_FROM} and
	 *            {@link BlipConnector#SUBS_DIRECTION_TO}. 0 also means both.
	 * @return {@link List} of type {@link Subscription} or null
	 * @throws BlipConnectorException
	 */
	public List<Subscription> getUserSubscriptions(String user, int dirOptions)
			throws BlipConnectorException {
		URI uri;
		try {
			StringBuilder sb = new StringBuilder().append(baseUrl).append(
					PATH_USERS).append("/").append(user).append(
					PATH_SUBSCRIPTIONS);
			if (dirOptions != 0
					&& dirOptions != (SUBS_DIRECTION_FROM | SUBS_DIRECTION_TO)) {
				if ((dirOptions & SUBS_DIRECTION_FROM) == SUBS_DIRECTION_FROM) {
					sb.append("/from");
				} else {
					sb.append("/to");
				}
			}// else no url mod, get ALL

			uri = new URI(sb.toString());
		} catch (URISyntaxException e1) {
			throw new BlipConnectorException("Error creating URI");
		}
		HttpResult out = execute(METHOD_GET, uri, null, null);
		String res = handleResult(out);
		if (res != null) {
			try {
				JSONArray array = new JSONArray(res);
				ArrayList<Subscription> ret = new ArrayList<Subscription>(array
						.length());
				for (int i = 0; i < array.length(); i++) {
					ret.add(new Subscription(array.getJSONObject(i)));
				}
				return ret;
			} catch (JSONException e) {
				throw new BlipConnectorException(e);
			}
		} else {
			return null;
		}
	}

}
